using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Diagnostics;
using System.Linq;

namespace E7.Protobuf
{
    internal class ProtobufUnityCompiler : AssetPostprocessor
    {
        /// <summary>
        /// Path to the file of all protobuf files in your Unity folder.
        /// </summary>
        static string[] AllProtoFiles
        {
            get
            {
                string[] protoFiles = Directory.GetFiles(Application.dataPath, "*.proto", SearchOption.AllDirectories);
                List<string> allfiles = new List<string>(protoFiles);
                if (Directory.Exists(ProtoPrefs.rawProtoPath))
                {
                    foreach (var file in Directory.GetFiles(ProtoPrefs.rawProtoPath, "*.proto",
                        SearchOption.AllDirectories))
                    {
                        if (!allfiles.Contains(file))
                            allfiles.Add(file);
                    }
                }

                return allfiles.ToArray();
            }
        }

        /// <summary>
        /// A parent folder of all protobuf files found in your Unity project collected together.
        /// This means all .proto files in Unity could import each other freely even if they are far apart.
        /// </summary>
        static string[] IncludePaths
        {
            get
            {
                string[] protoFiles = AllProtoFiles;

                List<string> includePaths = new List<string>(10);
                for (int i = 0; i < protoFiles.Length; i++)
                {
                    string protoFolder = Path.GetDirectoryName(protoFiles[i]);

                    if (!includePaths.Contains(protoFolder))
                        includePaths.Add(protoFolder);
                }

                if (!string.IsNullOrWhiteSpace(ProtoPrefs.optionalSearchPath))
                    includePaths.Add(ProtoPrefs.optionalSearchPath);
                return includePaths.ToArray();
            }
        }

        static bool anyChanges = false;

        static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
        {
#if !MAP_EDITOR
            anyChanges = false;
            if (ProtoPrefs.enabled == false)
            {
                return;
            }
            List<string> imports = new List<string>(importedAssets.Length);
            foreach (string assetPath in importedAssets)
            {
               string path = Path.Combine(Directory.GetParent(Application.dataPath).FullName,assetPath);
               imports.Add(path);
            }
            if (CompileProtobufSystemPath(imports.ToArray(), IncludePaths, ProtoPrefs.rawProtoCodePath))
                anyChanges = true;
            if (anyChanges)
            {
                UnityEngine.Debug.Log(nameof(ProtobufUnityCompiler));
                AssetDatabase.Refresh();
            }
#endif
        }

        /// <summary>
        /// Called from Force Compilation button in the prefs.
        /// </summary>
        internal static void CompileAllInProject()
        {
            if (ProtoPrefs.logStandard)
            {
                UnityEngine.Debug.Log("Protobuf Unity : Compiling all .proto files in the project...");
            }

            string csharp_out = ProtoPrefs.rawProtoCodePath;
            CompileProtobufSystemPath(AllProtoFiles, IncludePaths, csharp_out);
            AssetDatabase.Refresh();
        }

        static string GetRelateive(string working, string path)
        {
            if (path.IndexOf(working) > 0)
                return "./" + path.Substring(working.Length + 1);
            return path;
        }

        private static bool CompileProtobufSystemPath(string[] protoFiles, string[] includePaths, string csharp_out_folder)
        {
            if (protoFiles == null || protoFiles.Length < 1)
                return false;
            bool isIncludeMode = includePaths != null && includePaths.Length > 0;
            //Do not compile changes coming from UPM package.
            List<string> protoPath = new List<string>(protoFiles.Length);
            string CurrentDirectory = System.IO.Directory.GetCurrentDirectory(); //Directory.GetParent(Application.dataPath).FullName
            foreach (var file in protoFiles)
            {
                if (Path.GetExtension(file) == ".proto")
                {
                    string rel = GetRelateive(CurrentDirectory, file);
                    if (isIncludeMode)
                        protoPath.Add(rel.Substring(rel.LastIndexOf(Path.DirectorySeparatorChar) + 1));
                    else
                        protoPath.Add(GetRelateive(CurrentDirectory, file));
                }
            }

            if (protoPath.Count > 0)
            {
                string outputPathRelative = csharp_out_folder;
                if (Directory.Exists(csharp_out_folder))
                    outputPathRelative = GetRelateive(CurrentDirectory, csharp_out_folder);
                else
                    outputPathRelative = GetRelateive(CurrentDirectory, Path.Combine(Application.dataPath, "csharp_out"));
                List<string> proto_path_relative = new List<string>();
                foreach (string s in includePaths)
                {
                    string includepath = GetRelateive(CurrentDirectory, s);
                    if (proto_path_relative.Contains(includepath))
                        continue;
                    proto_path_relative.Add(includepath);
                }

                string options = $" --csharp_out={outputPathRelative}";
                if (isIncludeMode)
                    options += $" --proto_path={string.Join(" --proto_path=", proto_path_relative)}";
                string protoFilesPramas = $" {string.Join(" ", protoPath)}";

                string finalArguments = options + protoFilesPramas;

                if (ProtoPrefs.logStandard)
                {
                    UnityEngine.Debug.Log("Protobuf Unity : Final arguments :\n" + finalArguments);
                }

                ProcessStartInfo startInfo = new ProcessStartInfo()
                {
                    FileName = ProtoPrefs.excPath,
                    Arguments = finalArguments,
                    WorkingDirectory = CurrentDirectory
                };

                Process proc = new Process() {StartInfo = startInfo};
                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardOutput = ProtoPrefs.logStandard;
                proc.StartInfo.RedirectStandardError = ProtoPrefs.logError;
                proc.Start();

                string output = proc.StandardOutput.ReadToEnd();
                string error = proc.StandardError.ReadToEnd();
                proc.WaitForExit();

                if (ProtoPrefs.logStandard)
                {
                    if (output != "")
                    {
                        UnityEngine.Debug.Log("Protobuf Unity : " + output);
                    }

                    UnityEngine.Debug.Log("Protobuf Unity : Compiled " + Path.GetFileName(protoFilesPramas));
                }

                if (ProtoPrefs.logError && error != "")
                {
                    UnityEngine.Debug.LogError("Protobuf Unity : " + error);
                }

                return true;
            }

            return false;
        }
    }
}